﻿// ****************************************************************************
// * Copyright (c) 2011 OddCode SARL
// *
// * Permission is hereby granted, free of charge, to any person obtaining a
// * copy of this software and associated documentation files (the "Software"),
// * to deal in the Software without restriction, including without limitation
// * the rights to use, copy, modify, merge, publish, distribute, sublicense,
// * and/or sell copies of the Software, and to permit persons to whom the
// * Software is furnished to do so, subject to the following conditions:
// *
// * The above copyright notice and this permission notice shall be included in
// * all copies or substantial portions of the Software.
// *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// * DEALINGS IN THE SOFTWARE.
// *
// ****************************************************************************

using System;
using System.Collections.Generic;
using ArchAssembler.Elements;

namespace ArchAssembler.PortableExecutable
{
    /// <summary>
    /// List the functions exported by an image
    /// </summary>
    public sealed class ExportTableElement : CompositeElement
    {
        // ********************************************************************

        private readonly Dictionary<string, ExportFunction> m_functions;
        private readonly string m_moduleName;
        private readonly VariableSizeStringElement m_moduleNameElement;

        public ExportTableElement(string moduleName)
        {
            m_moduleName = moduleName;
            m_moduleNameElement = new VariableSizeStringElement(m_moduleName);
            m_functions = new Dictionary<string, ExportFunction>();
        }

        /// <summary>
        /// Export a function
        /// </summary>
        /// <param name="function">Function to export</param>
        /// <param name="name">Public name of the exported function</param>
        public void Export(Element function, string name)
        {
            if (!m_functions.ContainsKey(name))
            {
                ExportFunction l_function = new ExportFunction(function, name);
                m_functions.Add(name, l_function);
            }
        }


        internal override IEnumerable<Element> GetElements()
        {
            // ****************************************************************

            List<ExportFunction> l_functions = new List<ExportFunction>(m_functions.Values);
            l_functions.Sort();

            ElementCollection l_content = new ElementCollection();
            ElementCollection l_header = l_content.ElementCollection();
            ElementCollection l_functionAddresses = l_content.ElementCollection();
            ElementCollection l_functionNames = l_content.ElementCollection();
            ElementCollection l_functionOrdinals = l_content.ElementCollection();
            ElementCollection l_names = l_content.ElementCollection();

            l_header.UInt32(0); // Export flags
            l_header.UInt32(0); // date
            l_header.UInt16(0); // major version
            l_header.UInt16(0); // minor version
            l_header.RelativeMemoryAddress32(m_moduleNameElement); // module name
            l_header.UInt32(1); // ordinal base

            l_header.UInt32((UInt32) l_functions.Count); // address table entries
            l_header.UInt32((UInt32) l_functions.Count); // number of name pointers
            l_header.RelativeMemoryAddress32(l_functionAddresses); // function addresses
            l_header.RelativeMemoryAddress32(l_functionNames); // function names
            l_header.RelativeMemoryAddress32(l_functionOrdinals); // function ordinals

            // Module name
            l_names.Elements.Add(m_moduleNameElement);

            for (int l_i = 0; l_i < l_functions.Count; l_i++)
            {
                ExportFunction l_function = l_functions[l_i];

                l_functionAddresses.RelativeMemoryAddress32(l_function.Function);
                l_functionNames.RelativeMemoryAddress32(l_function.NameElement);
                l_functionOrdinals.UInt16((UInt16) l_i);

                l_names.Elements.Add(l_function.NameElement);
            }

            return l_content.Elements;
        }

        #region Nested type: ExportFunction

        private sealed class ExportFunction : IComparable<ExportFunction>
        {
            private readonly Element m_function;
            private readonly string m_name;
            private readonly VariableSizeStringElement m_nameElement;

            public ExportFunction(Element function, string name)
            {
                m_function = function;
                m_name = name;
                m_nameElement = new VariableSizeStringElement(name);
            }

            public Element Function
            {
                get { return m_function; }
            }

            public VariableSizeStringElement NameElement
            {
                get { return m_nameElement; }
            }

            #region IComparable<ExportFunction> Members

            public int CompareTo(ExportFunction other)
            {
                return m_name.CompareTo(other.m_name);
            }

            #endregion
        }

        #endregion
    }
}